package com.mikelouis.test.scala.coursera.reactive

/**
 * Created by yuazhu on 5/27/2015.
 */
object RandomValueGenerator {

  trait Generator[+T] {
    self =>
    // an alias for "this"

    def generate: T

    def map[S](f: T => S): Generator[S] = new Generator[S] {
      //      override def generate: S = f(self.generate)
      override def generate: S = f(Generator.this.generate)
    }

    def flatMap[S](f: T => Generator[S]): Generator[S] = new Generator[S] {
      override def generate: S = f(self.generate).generate
    }
  }

  val integers = new Generator[Int] {
    val rand = new java.util.Random()

    override def generate: Int = rand.nextInt()
  }

  //  val booleans = new Generator[Boolean] {
  //    override def generate: Boolean = integers.generate > 0
  //  }
  //
  //  val pairs = new Generator[(Int, Int)] {
  //    override def generate: (Int, Int) = (integers.generate, integers.generate)
  //  }

  val booleans = for (x <- integers) yield x > 0 // compiler will expand to integers map{ x => x > 0 }

  def pairs[T, U](t: Generator[T], u: Generator[U]) = for {
    x <- t
    y <- u
  } yield (x, y)

  def single[T](x: T): Generator[T] = new Generator[T] {
    override def generate: T = x
  }

  def choose(lo: Int, hi: Int): Generator[Int] = for (x <- integers) yield lo + x % (hi - lo)

  /**
   * return the given value
   * @param xs range of values to
   * @tparam T
   * @return
   */
  def oneOf[T](xs: T*): Generator[T] = for (idx <- choose(0, xs.length)) yield xs(idx)

  /**
   * Random value generator for List
   * @return
   */
  def lists: Generator[List[Int]] = for {
    isEmpty <- booleans
    list <- if (isEmpty) emptyLists else nonEmptyLists
  } yield list

  def emptyLists = single(Nil)

  def nonEmptyLists = for {
    head <- integers
    tail <- lists
  } yield head :: tail

  // exercise
  /**
   * Random value generator for Tree
   */
  trait Tree

  case class Inner(left: Tree, right: Tree) extends Tree

  case class Leaf(x: Int) extends Tree

  def leafs: Generator[Leaf] = for {
    x <- integers
  } yield Leaf(x)

  def inners: Generator[Inner] = for {
    l <- trees
    r <- trees
  } yield Inner(l, r)

  def trees: Generator[Tree] = for {
    isLeaf <- booleans
    tree <- if (isLeaf) leafs else inners
  } yield tree

  def test[T](g: Generator[T], numTimes: Int = 100)(test: T => Boolean): Unit = {
    for (i <- 0 until numTimes) {
      val value = g.generate
      assert(test(value), "test failed for " + value)
    }
    println("passed " + numTimes + " tests")
  }


  def main(args: Array[String]) {
    // Leaf(-836439862)
    // Inner(Leaf(1214772164),Leaf(-1394020132))
    // Inner(Inner(Inner(Leaf(-1319050433),Leaf(2124598751)),Leaf(663468117)),Leaf(1576691410))
    println(trees.generate)

    test(pairs(lists, lists)) {
      case (xs, ys) => (xs ++ ys).length >= xs.length
    }

  }

}
